home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / share / kde4 / apps / printer-applet / printer-applet.py < prev    next >
Encoding:
Python Source  |  2008-10-23  |  35.5 KB  |  958 lines

  1. #!/usr/bin/env python
  2.  
  3. #############################################################################
  4. ##
  5. ## Copyright 2007-2008 Canonical Ltd
  6. ## Author: Jonathan Riddell <jriddell@ubuntu.com>
  7. ##
  8. ## Includes code from System Config Printer
  9. ## Copyright 2007 Tim Waugh <twaugh@redhat.com>
  10. ## Copyright 2007 Red Hat, Inc.
  11. ##
  12. ## This program is free software; you can redistribute it and/or
  13. ## modify it under the terms of the GNU General Public License as
  14. ## published by the Free Software Foundation; either version 2 of 
  15. ## the License, or (at your option) any later version.
  16. ##
  17. ## This program is distributed in the hope that it will be useful,
  18. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20. ## GNU General Public License for more details.
  21. ##
  22. ## You should have received a copy of the GNU General Public License
  23. ## along with this program.  If not, see <http://www.gnu.org/licenses/>.
  24. ##
  25. #############################################################################
  26.  
  27. """
  28. A systray applet to show that documents are being printed, show printer warnings and errors and a GUI for hal-cups-utils automatic setup for new printers.
  29.  
  30. It is a Qt port of the applet from Red Hat's System Config Printer
  31. http://cyberelk.net/tim/software/system-config-printer/
  32. svn co http://svn.fedorahosted.org/svn/system-config-printer/trunk
  33. """
  34.  
  35. import os
  36. import subprocess
  37. import sys
  38.  
  39. SYSTEM_CONFIG_PRINTER_DIR = "/usr/share/system-config-printer"
  40.  
  41. MIN_REFRESH_INTERVAL = 1 # seconds
  42. CONNECTING_TIMEOUT = 60 # seconds
  43.  
  44. import time
  45.  
  46. from PyQt4.QtCore import *
  47. from PyQt4.QtGui import *
  48. from PyQt4 import uic
  49. from PyKDE4.kdecore import i18n, i18nc, i18np, i18ncp, ki18n, KAboutData, KCmdLineArgs, KCmdLineOptions, KStandardDirs, KLocalizedString
  50. from PyKDE4.kdeui import KApplication, KXmlGuiWindow, KStandardAction, KIcon, KToggleAction
  51.  
  52. if QFile.exists(SYSTEM_CONFIG_PRINTER_DIR + "/ppds.py"):
  53.     AUTOCONFIGURE = True
  54. else:
  55.     AUTOCONFIGURE = False
  56.  
  57. def translate(self, prop):
  58.     """reimplement method from uic to change it to use gettext"""
  59.     if prop.get("notr", None) == "true":
  60.         return self._cstring(prop)
  61.     else:
  62.         if prop.text is None:
  63.             return ""
  64.         text = prop.text.encode("UTF-8")
  65.         return i18n(text)
  66.  
  67. uic.properties.Properties._string = translate
  68.  
  69. import cups
  70.  
  71. import dbus
  72. import dbus.mainloop.qt
  73. import dbus.service
  74.  
  75. class MainWindow(KXmlGuiWindow):
  76.     """Our main GUI dialogue, overridden so that closing it doesn't quit the app"""
  77.  
  78.     def closeEvent(self, event):
  79.         event.ignore()
  80.         self.hide()
  81.  
  82. class PrintersWindow(QWidget):
  83.     """The printer status dialogue, overridden so that closing it doesn't quit the app and to untick the show menu entry"""
  84.  
  85.     def __init__(self, applet):
  86.         QWidget.__init__(self)
  87.         self.applet = applet
  88.  
  89.     def closeEvent(self, event):
  90.         event.ignore()
  91.         self.applet.on_printer_status_delete_event()
  92.         self.hide()
  93.  
  94. class StateReason:
  95.     REPORT=1
  96.     WARNING=2
  97.     ERROR=3
  98.  
  99.     LEVEL_ICON={
  100.         REPORT: "dialog-info",
  101.         WARNING: "dialog-warning",
  102.         ERROR: "dialog-error"
  103.         }
  104.  
  105.     def __init__(self, printer, reason):
  106.         self.printer = printer
  107.         self.reason = reason
  108.         self.level = None
  109.         self.canonical_reason = None
  110.  
  111.     def get_printer (self):
  112.         return self.printer
  113.  
  114.     def get_level (self):
  115.         if self.level != None:
  116.             return self.level
  117.  
  118.         if (self.reason.endswith ("-report") or
  119.             self.reason == "connecting-to-device"):
  120.             self.level = self.REPORT
  121.         elif self.reason.endswith ("-warning"):
  122.             self.level = self.WARNING
  123.         else:
  124.             self.level = self.ERROR
  125.         return self.level
  126.  
  127.     def get_reason (self):
  128.         if self.canonical_reason:
  129.             return self.canonical_reason
  130.  
  131.         level = self.get_level ()
  132.         reason = self.reason
  133.         if level == self.WARNING and reason.endswith ("-warning"):
  134.             reason = reason[:-8]
  135.         elif level == self.ERROR and reason.endswith ("-error"):
  136.             reason = reason[:-6]
  137.         self.canonical_reason = reason
  138.         return self.canonical_reason
  139.  
  140.     def get_description (self):
  141.         messages = {
  142.             'toner-low': (i18n("Toner low"),
  143.                           ki18n("Printer '%1' is low on toner.")),
  144.             'toner-empty': (i18n("Toner empty"),
  145.                             ki18n("Printer '%1' has no toner left.")),
  146.             'cover-open': (i18n("Cover open"),
  147.                            ki18n("The cover is open on printer '%1'.")),
  148.             'door-open': (i18n("Door open"),
  149.                           ki18n("The door is open on printer '%1'.")),
  150.             'media-low': (i18n("Paper low"),
  151.                           ki18n("Printer '%1' is low on paper.")),
  152.             'media-empty': (i18n("Out of paper"),
  153.                             ki18n("Printer '%1' is out of paper.")),
  154.             'marker-supply-low': (i18n("Ink low"),
  155.                                   ki18n("Printer '%1' is low on ink.")),
  156.             'marker-supply-empty': (i18n("Ink empty"),
  157.                                     ki18n("Printer '%1' has no ink left.")),
  158.             'connecting-to-device': (i18n("Not connected?"),
  159.                                      ki18n("Printer '%1' may not be connected.")),
  160.             }
  161.         try:
  162.             (title, text) = messages[self.get_reason ()]
  163.             text = text.subs (self.get_printer ()).toString ()
  164.         except KeyError:
  165.             if self.get_level () == self.REPORT:
  166.                 title = i18n("Printer report")
  167.             elif self.get_level () == self.WARNING:
  168.                 title = i18n("Printer warning")
  169.             elif self.get_level () == self.ERROR:
  170.                 title = i18n("Printer error")
  171.             text = i18n("Printer '%1': '%2'.", self.get_printer (), self.get_reason ())
  172.         return (title, text)
  173.  
  174.     def get_tuple (self):
  175.         return (self.get_level (), self.get_printer (), self.get_reason ())
  176.  
  177.     def __cmp__(self, other):
  178.         if other == None:
  179.             return 1
  180.         if other.get_level () != self.get_level ():
  181.             return self.get_level () < other.get_level ()
  182.         if other.get_printer () != self.get_printer ():
  183.             return other.get_printer () < self.get_printer ()
  184.         return other.get_reason () < self.get_reason ()
  185.  
  186. def collect_printer_state_reasons (connection):
  187.     result = []
  188.     printers = connection.getPrinters ()
  189.     for name, printer in printers.iteritems ():
  190.         reasons = printer["printer-state-reasons"]
  191.         if type (reasons) == str:
  192.             # Work around a bug that was fixed in pycups-1.9.20.
  193.             reasons = [reasons]
  194.         for reason in reasons:
  195.             if reason == "none":
  196.                 break
  197.             if (reason.startswith ("moving-to-paused") or
  198.                 reason.startswith ("paused") or
  199.                 reason.startswith ("shutdown") or
  200.                 reason.startswith ("stopping") or
  201.                 reason.startswith ("stopped-partly")):
  202.                 continue
  203.             result.append (StateReason (name, reason))
  204.     return result
  205.  
  206. def worst_printer_state_reason (connection, printer_reasons=None):
  207.     """Fetches the printer list and checks printer-state-reason for
  208.     each printer, returning a StateReason for the most severe
  209.     printer-state-reason, or None."""
  210.     worst_reason = None
  211.     if printer_reasons == None:
  212.         printer_reasons = collect_printer_state_reasons (connection)
  213.     for reason in printer_reasons:
  214.         if worst_reason == None:
  215.             worst_reason = reason
  216.             continue
  217.         if reason > worst_reason:
  218.             worst_reason = reason
  219.  
  220.     return worst_reason
  221.  
  222.  
  223. class JobManager(QObject):
  224.     """our main class creates the systray icon and the dialogues and refreshes the dialogues for new information"""
  225.     def __init__(self, parent = None):
  226.         QObject.__init__(self)
  227.  
  228.         self.will_refresh = False # whether timeout is set
  229.         self.last_refreshed = 0
  230.         self.trayicon = True
  231.         self.suppress_icon_hide = False
  232.         self.which_jobs = "not-completed"
  233.         self.hidden = False
  234.         self.jobs = {}
  235.         self.jobiters = {}
  236.         self.will_update_job_creation_times = False # whether timeout is set
  237.         self.update_job_creation_times_timer = QTimer(self)
  238.         self.connect(self.update_job_creation_times_timer, SIGNAL("timeout()"), self.update_job_creation_times)
  239.         self.statusbar_set = False
  240.         self.reasons_seen = {}
  241.         self.connecting_to_device = {} # dict of printer->time first seen
  242.         self.still_connecting = set()
  243.         self.special_status_icon = False
  244.  
  245.         #Use local files if in current directory
  246.         if os.path.exists("printer-applet.ui"):
  247.             APPDIR = QDir.currentPath()
  248.         else:
  249.             file =  KStandardDirs.locate("appdata", "printer-applet.ui")
  250.             APPDIR = file.left(file.lastIndexOf("/"))
  251.  
  252.         self.mainWindow = MainWindow()
  253.         uic.loadUi(APPDIR + "/" + "printer-applet.ui", self.mainWindow)
  254.  
  255.         self.printersWindow = PrintersWindow(self)
  256.         uic.loadUi(APPDIR + "/" + "printer-applet-printers.ui", self.printersWindow)
  257.  
  258.         self.sysTray = QSystemTrayIcon(KIcon("printer"), self.mainWindow)
  259.         #self.sysTray.show()
  260.         self.connect(self.sysTray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.showMainWindow)
  261.  
  262.         self.menu = QMenu()
  263.         self.menu.addAction(i18n("_Hide").replace("_", ""), self.on_icon_hide_activate)
  264.         self.menu.addAction(KIcon("application-exit"), i18n("Quit"), self.on_icon_quit_activate)
  265.         self.sysTray.setContextMenu(self.menu)
  266.  
  267.         self.mainWindow.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
  268.         self.connect(self.mainWindow.treeWidget, SIGNAL("customContextMenuRequested(const QPoint&)"), self.on_treeview_button_press_event)
  269.         #self.connect(self.mainWindow.treeWidget, SIGNAL("itemClicked(QTreeWidgetItem*, int)"), self.printItemClicked)
  270.         self.rightClickMenu = QMenu(self.mainWindow.treeWidget)
  271.         self.cancel = self.rightClickMenu.addAction(i18n("Cancel"), self.on_job_cancel_activate)
  272.         self.hold = self.rightClickMenu.addAction(i18n("_Hold").replace("_",""), self.on_job_hold_activate)
  273.         self.release = self.rightClickMenu.addAction(i18n("_Release").replace("_",""), self.on_job_release_activate)
  274.         self.reprint = self.rightClickMenu.addAction(i18n("Re_print").replace("_",""), self.on_job_reprint_activate)
  275.  
  276.         closeAction = KStandardAction.close(self.hideMainWindow, self.mainWindow.actionCollection());
  277.  
  278.         refreshAction = self.mainWindow.actionCollection().addAction("refresh")
  279.         refreshAction.setIcon( KIcon("view-refresh") )
  280.         refreshAction.setText( i18n( "&Refresh" ) )
  281.         refreshAction.setShortcut(QKeySequence(Qt.Key_F5))
  282.         self.connect(refreshAction, SIGNAL("triggered(bool)"), self.refresh);
  283.  
  284.         showCompletedJobsAction = KToggleAction("Show Completed Jobs", self.mainWindow)
  285.         self.mainWindow.actionCollection().addAction("show_completed_jobs", showCompletedJobsAction)
  286.         self.connect(showCompletedJobsAction, SIGNAL("triggered(bool)"), self.on_show_completed_jobs_activate);
  287.  
  288.         showPrinterStatusAction = KToggleAction("Show Printer Status", self.mainWindow)
  289.         self.mainWindow.actionCollection().addAction("show_printer_status", showPrinterStatusAction)
  290.         self.connect(showPrinterStatusAction, SIGNAL("triggered(bool)"), self.on_show_printer_status_activate);
  291.  
  292.         self.mainWindow.createGUI(APPDIR + "/printer-appletui.rc")
  293.  
  294.         cups.setPasswordCB(self.cupsPasswdCallback)
  295.  
  296.         dbus.mainloop.qt.DBusQtMainLoop(set_as_default=True)
  297.  
  298.         try:
  299.             bus = dbus.SystemBus()
  300.         except:
  301.             print >> sys.stderr, "%s: printer-applet failed to connect to system D-Bus"
  302.             sys.exit (1)
  303.  
  304.         if AUTOCONFIGURE:
  305.             notification = NewPrinterNotification(bus, self)
  306.  
  307.         # D-Bus
  308.         bus.add_signal_receiver (self.handle_dbus_signal,
  309.                                  path="/com/redhat/PrinterSpooler",
  310.                                  dbus_interface="com.redhat.PrinterSpooler")
  311.         self.refresh()
  312.  
  313.     """Used in gtk frontend to set magnifing glass icon when configuring printer, I don't have a suitable icon so using bubbles instead
  314.     # Handle "special" status icon
  315.     def set_special_statusicon (self, iconname):
  316.         self.special_status_icon = True
  317.         self.statusicon.set_from_icon_name (iconname)
  318.         self.set_statusicon_visibility ()
  319.  
  320.     def unset_special_statusicon (self):
  321.         self.special_status_icon = False
  322.         self.statusicon.set_from_pixbuf (self.saved_statusicon_pixbuf)
  323.     """
  324.  
  325.     def notify_new_printer (self, printer, title, text):
  326.         self.hidden = False
  327.         self.showMessage(title, text)
  328.  
  329.     def showMessage(self, title, message):
  330.         """show a message, delayed slightly to ensure the systray is visible else it appears in the wrong place
  331.         Gtk uses libnotify, for Qt we just show the message directly"""
  332.         self.sysTray.show()
  333.         self.sysTrayTitle = title
  334.         self.sysTrayMessage = message
  335.         QTimer.singleShot(1000, self.showSysTrayMessage)
  336.  
  337.     def showSysTrayMessage(self):
  338.         self.sysTray.showMessage(self.sysTrayTitle, self.sysTrayMessage)
  339.  
  340.     """unused, see set_special_statusicon
  341.     def set_statusicon_from_pixbuf (self, pb):
  342.         self.saved_statusicon_pixbuf = pb
  343.         if not self.special_status_icon:
  344.             self.statusicon.set_from_pixbuf (pb)
  345.     """
  346.  
  347.     """unused, see MainWindow and PrintersWindow
  348.     def on_delete_event(self, *args):
  349.         if self.trayicon:
  350.             self.MainWindow.hide ()
  351.             if self.show_printer_status.get_active ():
  352.                 self.PrintersWindow.hide ()
  353.         else:
  354.             self.loop.quit ()
  355.         return True
  356.     """
  357.  
  358.     def on_printer_status_delete_event(self):
  359.         self.mainWindow.actionShow_Printer_Status.setChecked(False)
  360.  
  361.     def cupsPasswdCallback(self, querystring):
  362.         (text, ok) = QInputDialog.getText(self.mainWindow, i18n("Password required"), querystring, QLineEdit.Password)
  363.         if ok:
  364.             print "ok"
  365.             return text
  366.         return ''
  367.  
  368.     def show_IPP_Error(self, exception, message):
  369.         if exception == cups.IPP_NOT_AUTHORIZED:
  370.             error_text = ('<span weight="bold" size="larger">' +
  371.                           i18n('Not authorized') + '</span>\n\n' +
  372.                           i18n('The password may be incorrect.'))
  373.         else:
  374.             error_text = ('<span weight="bold" size="larger">' +
  375.                           i18n('CUPS server error') + '</span>\n\n' +
  376.                           i18n("There was an error during the CUPS "\
  377.                             "operation: '%1'.", message))
  378.         #fix Gtk's non-HTML for Qt
  379.         error_text = error_text.replace("\n", "<br />")
  380.         error_text = error_text.replace("span", "strong")
  381.         QMessageBox.critical(self.mainWindow, i18n("Error"), error_text)
  382.  
  383.     """
  384.     def toggle_window_display(self, icon):
  385.     """
  386.     #FIXME, hide printer status window?
  387.     def hideMainWindow(self):
  388.         self.mainWindow.hide()
  389.  
  390.     def showMainWindow(self, activationReason):
  391.         if activationReason == QSystemTrayIcon.Trigger:
  392.             if self.mainWindow.isVisible():
  393.                 self.mainWindow.hide()
  394.             else:
  395.                 self.mainWindow.show()
  396.                 self.refresh()
  397.  
  398.     def on_show_completed_jobs_activate(self, activated):
  399.         if activated:
  400.             self.which_jobs = "all"
  401.         else:
  402.             self.which_jobs = "not-completed"
  403.         self.refresh()
  404.  
  405.     def on_show_printer_status_activate(self, activated):
  406.         if activated:
  407.             self.printersWindow.show()
  408.         else:
  409.             self.printersWindow.hide()
  410.  
  411.     def check_still_connecting(self):
  412.         """Timer callback to check on connecting-to-device reasons."""
  413.         c = cups.Connection ()
  414.         printer_reasons = collect_printer_state_reasons (c)
  415.         del c
  416.  
  417.         if self.update_connecting_devices (printer_reasons):
  418.             self.refresh ()
  419.  
  420.         # Don't run this callback again.
  421.         return False
  422.  
  423.     def update_connecting_devices(self, printer_reasons=[]):
  424.         """Updates connecting_to_device dict and still_connecting set.
  425.         Returns True if a device has been connecting too long."""
  426.         time_now = time.time ()
  427.         connecting_to_device = {}
  428.         trouble = False
  429.         for reason in printer_reasons:
  430.             if reason.get_reason () == "connecting-to-device":
  431.                 # Build a new connecting_to_device dict.  If our existing
  432.                 # dict already has an entry for this printer, use that.
  433.                 printer = reason.get_printer ()
  434.                 t = self.connecting_to_device.get (printer, time_now)
  435.                 connecting_to_device[printer] = t
  436.                 if time_now - t >= CONNECTING_TIMEOUT:
  437.                     trouble = True
  438.  
  439.         # Clear any previously-notified errors that are now fine.
  440.         remove = set()
  441.         for printer in self.still_connecting:
  442.             if not self.connecting_to_device.has_key (printer):
  443.                 remove.add (printer)
  444.  
  445.         self.still_connecting = self.still_connecting.difference (remove)
  446.  
  447.         self.connecting_to_device = connecting_to_device
  448.         return trouble
  449.  
  450.     def check_state_reasons(self, connection, my_printers=set()):
  451.         printer_reasons = collect_printer_state_reasons (connection)
  452.  
  453.         # Look for any new reasons since we last checked.
  454.         old_reasons_seen_keys = self.reasons_seen.keys ()
  455.         reasons_now = set()
  456.         need_recheck = False
  457.         for reason in printer_reasons:
  458.             tuple = reason.get_tuple ()
  459.             printer = reason.get_printer ()
  460.             reasons_now.add (tuple)
  461.             if not self.reasons_seen.has_key (tuple):
  462.                 # New reason.
  463.                 iter = QTreeWidgetItem(self.printersWindow.treeWidget)
  464.                 #iter.setText(0, reason.get_level ())
  465.                 iter.setText(0, reason.get_printer ())
  466.                 title, text = reason.get_description ()
  467.                 iter.setText(1, text)
  468.                 self.printersWindow.treeWidget.addTopLevelItem(iter)
  469.  
  470.                 self.reasons_seen[tuple] = iter
  471.                 if (reason.get_reason () == "connecting-to-device" and
  472.                     not self.connecting_to_device.has_key (printer)):
  473.                     # First time we've seen this.
  474.                     need_recheck = True
  475.  
  476.         if need_recheck:
  477.             # Check on them again in a minute's time.
  478.             QTimer.singleShot(CONNECTING_TIMEOUT * 1000, self.check_still_connecting)
  479.  
  480.         self.update_connecting_devices (printer_reasons)
  481.         items = self.reasons_seen.keys ()
  482.         for tuple in items:
  483.             if not tuple in reasons_now:
  484.                 # Reason no longer present.
  485.                 iter = self.reasons_seen[tuple]
  486.                 index = self.mainWindow.treeWidget.indexOfTopLevelItem(iter)
  487.                 self.mainWindow.treeWidget.takeTopLevelItem(index)
  488.                 del self.reasons_seen[tuple]
  489.         # Update statusbar and icon with most severe printer reason
  490.         # across all printers.
  491.         self.icon_has_emblem = False
  492.         reason = worst_printer_state_reason (connection, printer_reasons)
  493.         if reason != None and reason.get_level () >= StateReason.WARNING:
  494.             title, text = reason.get_description ()
  495.             #if self.statusbar_set:
  496.             #    self.statusbar.pop (0)
  497.             self.mainWindow.statusBar().showMessage(text)
  498.             #self.statusbar.push (0, text)
  499.             self.worst_reason_text = text
  500.             self.statusbar_set = True
  501.  
  502.             if self.trayicon:
  503.                 icon = StateReason.LEVEL_ICON[reason.get_level ()]
  504.                 emblem = QPixmap(KIcon(icon).pixmap(16, 16))
  505.                 pixbuf = QPixmap(KIcon("printer").pixmap(22, 22))
  506.                 painter = QPainter(pixbuf)
  507.                 painter.drawPixmap(pixbuf.width()-emblem.width(),pixbuf.height()-emblem.height(),emblem)
  508.                 painter.end()
  509.                 self.sysTray.setIcon(QIcon(pixbuf))
  510.                 self.icon_has_emblem = True
  511.         else:
  512.             # No errors
  513.             if self.statusbar_set:
  514.                 #self.statusbar.pop (0)
  515.                 self.mainWindow.statusBar().clearMessage()
  516.                 self.statusbar_set = False
  517.  
  518.     """not using notifications in qt frontend
  519.     def on_notification_closed(self, notify):
  520.     """
  521.  
  522.     def update_job_creation_times(self):
  523.         now = time.time ()
  524.         need_update = False
  525.         for job, data in self.jobs.iteritems():
  526.             if self.jobs.has_key (job):
  527.                 iter = self.jobiters[job]
  528.  
  529.             t = "Unknown"
  530.             if data.has_key ('time-at-creation'):
  531.                 created = data['time-at-creation']
  532.                 ago = now - created
  533.                 if ago > 86400:
  534.                     t = time.ctime (created)
  535.                 elif ago > 3600:
  536.                     need_update = True
  537.                     hours = int (ago / 3600)
  538.                     mins = int ((ago % 3600) / 60)
  539.                     if mins > 0:
  540.                         th = unicode(i18ncp("%1 in the '%1 and %2 ago' message below", "1 hour", "%1 hours", hours), 'utf-8')
  541.                         tm = unicode(i18ncp("%2 in the '%1 and %2 ago' message below", "1 minute", "%1 minutes", hours), 'utf-8')
  542.                         t = i18nc("Arguments are formatted hours and minutes from the messages above", "%1 and %2 ago", th, tm)
  543.                     else:
  544.                         t = i18np("1 hour ago", "%1 hours ago", hours)
  545.                 else:
  546.                     need_update = True
  547.                     mins = int(ago / 60)
  548.                     t = i18np("a minute ago", "%1 minutes ago", mins)
  549.  
  550.             #self.store.set_value (iter, 4, t)
  551.             iter.setText(4, t)
  552.  
  553.         if need_update and not self.will_update_job_creation_times:
  554.             self.update_job_creation_times_timer.setInterval(60 * 1000)
  555.             self.update_job_creation_times_timer.start()
  556.             self.will_update_job_creation_times = True
  557.  
  558.         if not need_update:
  559.             self.update_job_creation_times_timer.stop()
  560.             self.will_update_job_creation_times = False
  561.  
  562.         # Return code controls whether the timeout will recur.
  563.         return self.will_update_job_creation_times
  564.  
  565.     def refresh(self):
  566.         """updates the print dialogue"""
  567.         now = time.time ()
  568.         if (now - self.last_refreshed) < MIN_REFRESH_INTERVAL:
  569.             if self.will_refresh:
  570.                 return
  571.  
  572.             #gobject.timeout_add (MIN_REFRESH_INTERVAL * 1000,
  573.             #                     self.refresh)
  574.             QTimer.singleShot(MIN_REFRESH_INTERVAL * 1000, self.update_job_creation_times)
  575.             self.will_refresh = True
  576.             return
  577.  
  578.         self.will_refresh = False
  579.         self.last_refreshed = now
  580.  
  581.         try:
  582.             c = cups.Connection ()
  583.             jobs = c.getJobs (which_jobs=self.which_jobs, my_jobs=True)
  584.         except cups.IPPError, (e, m):
  585.             self.show_IPP_Error (e, m)
  586.             return
  587.         except RuntimeError:
  588.             return
  589.  
  590.         if self.which_jobs == "not-completed":
  591.             num_jobs = len (jobs)
  592.         else:
  593.             try:
  594.                 num_jobs = len (c.getJobs (my_jobs=True))
  595.             except cups.IPPError, (e, m):
  596.                 self.show_IPP_Error (e, m)
  597.                 return
  598.             except RuntimeError:
  599.                 return
  600.  
  601.         if self.trayicon:
  602.             self.num_jobs = num_jobs
  603.             if self.hidden and self.num_jobs != self.num_jobs_when_hidden:
  604.                 self.hidden = False
  605.             if num_jobs == 0:
  606.                 tooltip = i18n("No documents queued")
  607.                 #FIXMEself.set_statusicon_from_pixbuf (self.icon_no_jobs)
  608.             else:
  609.                 tooltip = i18np("1 document queued", "%1 documents queued", num_jobs)
  610.                 #self.set_statusicon_from_pixbuf (self.icon_jobs)
  611.  
  612.         my_printers = set()
  613.         for job, data in jobs.iteritems ():
  614.             state = data.get ('job-state', cups.IPP_JOB_CANCELED)
  615.             if state >= cups.IPP_JOB_CANCELED:
  616.                 continue
  617.             uri = data.get ('job-printer-uri', '/')
  618.             i = uri.rfind ('/')
  619.             my_printers.add (uri[i + 1:])
  620.  
  621.         self.check_state_reasons (c, my_printers)
  622.         del c
  623.  
  624.         if self.trayicon:
  625.             # If there are no jobs but there is a printer
  626.             # warning/error indicated by the icon, set the icon
  627.             # tooltip to the reason description.
  628.             if self.num_jobs == 0 and self.icon_has_emblem:
  629.                 tooltip = self.worst_reason_text
  630.  
  631.             self.sysTray.setToolTip (tooltip)
  632.             self.set_statusicon_visibility ()
  633.  
  634.         for job in self.jobs:
  635.             if not jobs.has_key (job):
  636.                 #self.store.remove (self.jobiters[job])
  637.                 index = self.mainWindow.treeWidget.indexOfTopLevelItem(self.jobiters[job])
  638.                 self.mainWindow.treeWidget.takeTopLevelItem(index)
  639.                 del self.jobiters[job]
  640.  
  641.         for job, data in jobs.iteritems():
  642.             if self.jobs.has_key (job):
  643.                 iter = self.jobiters[job]
  644.             else:
  645.                 iter = QTreeWidgetItem(self.mainWindow.treeWidget)
  646.                 iter.setText(0, str(job))
  647.                 iter.setText(1, data.get('job-name', 'Unknown'))
  648.                 self.mainWindow.treeWidget.addTopLevelItem(iter)
  649.                 self.jobiters[job] = iter
  650.  
  651.             uri = data.get('job-printer-uri', '')
  652.             i = uri.rfind ('/')
  653.             if i != -1:
  654.                 printer = uri[i + 1:]
  655.             iter.setText(2, printer)
  656.  
  657.             if data.has_key ('job-k-octets'):
  658.                 size = str (data['job-k-octets']) + 'k'
  659.             else:
  660.                 size = 'Unknown'
  661.             iter.setText(3, size)
  662.             #self.store.set_value (iter, 3, size)
  663.  
  664.             state = None
  665.             if data.has_key ('job-state'):
  666.                 try:
  667.                     jstate = data['job-state']
  668.                     s = int (jstate)
  669.                     state = { cups.IPP_JOB_PENDING:i18nc("Job state", "Pending"),
  670.                               cups.IPP_JOB_HELD:i18nc("Job state", "Held"),
  671.                               cups.IPP_JOB_PROCESSING: i18nc("Job state", "Processing"),
  672.                               cups.IPP_JOB_STOPPED: i18nc("Job state", "Stopped"),
  673.                               cups.IPP_JOB_CANCELED: i18nc("Job state", "Canceled"),
  674.                               cups.IPP_JOB_ABORTED: i18nc("Job state", "Aborted"),
  675.                               cups.IPP_JOB_COMPLETED: i18nc("Job state", "Completed") }[s]
  676.                 except ValueError:
  677.                     pass
  678.                 except IndexError:
  679.                     pass    
  680.             if state == None:
  681.                 state = i18nc("Job state", "Unknown")
  682.             iter.setText(5, state)
  683.             columns = self.mainWindow.treeWidget.columnCount()
  684.             for i in range(columns):
  685.                 self.mainWindow.treeWidget.resizeColumnToContents(i)
  686.  
  687.         self.jobs = jobs
  688.         self.update_job_creation_times ()
  689.  
  690.     def set_statusicon_visibility (self):
  691.         if self.trayicon:
  692.             if self.suppress_icon_hide:
  693.                 # Avoid hiding the icon if we've been woken up to notify
  694.                 # about a new printer.
  695.                 self.suppress_icon_hide = False
  696.                 return
  697.  
  698.             if (not self.hidden) and (self.num_jobs > 0 or self.icon_has_emblem) or self.special_status_icon:
  699.                 self.sysTray.show()
  700.             else:
  701.                 self.sysTray.hide()
  702.  
  703.     def on_treeview_button_press_event(self, postition):
  704.         # Right-clicked.
  705.         items = self.mainWindow.treeWidget.selectedItems ()
  706.         print "items" + str(items)
  707.         print len(items)
  708.         if len(items) != 1:
  709.             return
  710.         print "selected: " + str(items)
  711.         iter = items[0]
  712.         if iter == None:
  713.             return
  714.  
  715.         self.jobid = int(iter.text(0))
  716.         job = self.jobs[self.jobid]
  717.         self.cancel.setEnabled (True)
  718.         self.hold.setEnabled (True)
  719.         self.release.setEnabled (True)
  720.         self.reprint.setEnabled (True)
  721.         if job.has_key ('job-state'):
  722.             s = job['job-state']
  723.             print s, "jobstate"
  724.             if s >= cups.IPP_JOB_CANCELED:
  725.                 self.cancel.setEnabled (False)
  726.             if s != cups.IPP_JOB_PENDING and s != cups.IPP_JOB_PROCESSING:
  727.                 self.hold.setEnabled (False)
  728.             if s != cups.IPP_JOB_HELD:
  729.                 self.release.setEnabled (False)
  730.             if (s != cups.IPP_JOB_CANCELED or
  731.                 not job.get('job-preserved', False)):
  732.                 self.reprint.setEnabled (False)
  733.         self.rightClickMenu.popup(QCursor.pos())
  734.  
  735.     def on_icon_popupmenu(self, icon, button, time):
  736.         self.icon_popupmenu.popup (None, None, None, button, time)
  737.  
  738.     def on_icon_hide_activate(self):
  739.         self.num_jobs_when_hidden = self.num_jobs
  740.         self.hidden = True
  741.         self.set_statusicon_visibility ()
  742.  
  743.     def on_icon_quit_activate(self):
  744.         app.quit()
  745.  
  746.     def on_job_cancel_activate(self):
  747.         try:
  748.             c = cups.Connection ()
  749.             c.cancelJob (self.jobid)
  750.             del c
  751.         except cups.IPPError, (e, m):
  752.             self.show_IPP_Error (e, m)
  753.             self.refresh()
  754.             return
  755.         except RuntimeError:
  756.             return
  757.         self.refresh()
  758.  
  759.     def on_job_hold_activate(self):
  760.         try:
  761.             c = cups.Connection ()
  762.             c.setJobHoldUntil (self.jobid, "indefinite")
  763.             del c
  764.         except cups.IPPError, (e, m):
  765.             self.show_IPP_Error (e, m)
  766.             self.refresh()
  767.             return
  768.         except RuntimeError:
  769.             return
  770.         self.refresh()
  771.  
  772.     def on_job_release_activate(self):
  773.         try:
  774.             c = cups.Connection ()
  775.             c.setJobHoldUntil (self.jobid, "no-hold")
  776.             del c
  777.         except cups.IPPError, (e, m):
  778.             self.show_IPP_Error (e, m)
  779.             self.refresh()
  780.             return
  781.         except RuntimeError:
  782.             return
  783.         self.refresh()
  784.  
  785.     def on_job_reprint_activate(self):
  786.         try:
  787.             c = cups.Connection ()
  788.             c.restartJob (self.jobid)
  789.             del c
  790.         except cups.IPPError, (e, m):
  791.             self.show_IPP_Error (e, m)
  792.             self.refresh()
  793.             return
  794.         except RuntimeError:
  795.             return
  796.  
  797.         self.refresh ()
  798.  
  799.     def on_refresh_activate(self, menuitem):
  800.         self.refresh ()
  801.  
  802.     def handle_dbus_signal(self, *args):
  803.         self.refresh ()
  804.  
  805.     ## Printer status window
  806.     """FIXME
  807.     def set_printer_status_icon (self, column, cell, model, iter, *user_data):
  808.         level = model.get_value (iter, 0)
  809.         icon = StateReason.LEVEL_ICON[level]
  810.         theme = gtk.icon_theme_get_default ()
  811.         try:
  812.             pixbuf = theme.load_icon (icon, 22, 0)
  813.             cell.set_property("pixbuf", pixbuf)
  814.         except gobject.GError, exc:
  815.             pass # Couldn't load icon
  816.     """
  817.     """FIXME
  818.     def set_printer_status_name (self, column, cell, model, iter, *user_data):
  819.         cell.set_property("text", model.get_value (iter, 1))
  820.     """
  821.  
  822. ####
  823. #### NewPrinterNotification DBus server (the 'new' way).  Note: this interface
  824. #### is not final yet.
  825. ####
  826. PDS_PATH="/com/redhat/NewPrinterNotification"
  827. PDS_IFACE="com.redhat.NewPrinterNotification"
  828. PDS_OBJ="com.redhat.NewPrinterNotification"
  829. class NewPrinterNotification(dbus.service.Object):
  830.     """listen for dbus signals"""
  831.     STATUS_SUCCESS = 0
  832.     STATUS_MODEL_MISMATCH = 1
  833.     STATUS_GENERIC_DRIVER = 2
  834.     STATUS_NO_DRIVER = 3
  835.  
  836.     def __init__ (self, bus, jobmanager):
  837.         self.bus = bus
  838.         self.getting_ready = 0
  839.         self.jobmanager = jobmanager
  840.         bus_name = dbus.service.BusName (PDS_OBJ, bus=bus)
  841.         dbus.service.Object.__init__ (self, bus_name, PDS_PATH)
  842.         #self.jobmanager.notify_new_printer ("", i18n("New Printer"), i18n("Configuring New Printer"))
  843.  
  844.     """
  845.     def wake_up (self):
  846.         global waitloop, runloop, jobmanager
  847.         do_imports ()
  848.         if jobmanager == None:
  849.             waitloop.quit ()
  850.             runloop = gobject.MainLoop ()
  851.             jobmanager = JobManager(bus, runloop,
  852.                                     service_running=service_running,
  853.                                     trayicon=trayicon, suppress_icon_hide=True)
  854.     """
  855.     
  856.     @dbus.service.method(PDS_IFACE, in_signature='', out_signature='')
  857.     def GetReady (self):
  858.         """hal-cups-utils is settings up a new printer"""
  859.         self.jobmanager.notify_new_printer ("", i18n("New Printer"), i18n("Configuring New Printer"))
  860.     """
  861.         self.wake_up ()
  862.         if self.getting_ready == 0:
  863.             jobmanager.set_special_statusicon (SEARCHING_ICON)
  864.  
  865.         self.getting_ready += 1
  866.         gobject.timeout_add (60 * 1000, self.timeout_ready)
  867.  
  868.     def timeout_ready (self):
  869.         global jobmanager
  870.         if self.getting_ready > 0:
  871.             self.getting_ready -= 1
  872.         if self.getting_ready == 0:
  873.             jobmanager.unset_special_statusicon ()
  874.  
  875.         return False
  876.     """
  877.  
  878.     # When I plug in my printer HAL calls this with these args:
  879.     #status: 0
  880.     #name: PSC_1400_series
  881.     #mfg: HP
  882.     #mdl: PSC 1400 series
  883.     #des:
  884.     #cmd: LDL,MLC,PML,DYN
  885.     @dbus.service.method(PDS_IFACE, in_signature='isssss', out_signature='')
  886.     def NewPrinter (self, status, name, mfg, mdl, des, cmd):
  887.         """hal-cups-utils has set up a new printer"""
  888.         """
  889.         print "status: " + str(status)
  890.         print "name: " + name
  891.         print "mfg: " + mfg
  892.         print "mdl: " + mdl
  893.         print "des: " + des
  894.         print "cmd: " + cmd
  895.         """
  896.  
  897.         c = cups.Connection ()
  898.         try:
  899.             printer = c.getPrinters ()[name]
  900.         except KeyError:
  901.             return
  902.         del c
  903.  
  904.         sys.path.append (SYSTEM_CONFIG_PRINTER_DIR)
  905.         from ppds import ppdMakeModelSplit
  906.         (make, model) = ppdMakeModelSplit (printer['printer-make-and-model'])
  907.         driver = make + " " + model
  908.         if status < self.STATUS_GENERIC_DRIVER:
  909.             title = i18n("Printer added")
  910.         else:
  911.             title = i18n("Missing printer driver")
  912.  
  913.         if status == self.STATUS_SUCCESS:
  914.             text = i18n("'%1' is ready for printing.", name)
  915.         else: # Model mismatch
  916.             text = i18n("'%1' has been added, using the '%2' driver.", name, driver)
  917.  
  918.         self.jobmanager.notify_new_printer (name, title, text)
  919.  
  920.  
  921. if __name__ == "__main__":
  922.     """start the application.  TODO, gtk frontend does clever things here to not start the GUI until it has to"""
  923.     appName     = "printer-applet"
  924.     catalogue   = "printer-applet"
  925.     programName = ki18n("Printer Applet")
  926.     version     = "1.0"
  927.     description = ki18n("Applet to view current print jobs and configure new printers")
  928.     license     = KAboutData.License_GPL
  929.     copyright   = ki18n("2007-2008 Canonical Ltd")
  930.     text        = KLocalizedString()
  931.     homePage    = "http://utils.kde.org/projects/printer-applet"
  932.     bugEmail    = ""
  933.  
  934.     aboutData   = KAboutData (appName, catalogue, programName, version, description,
  935.                                 license, copyright, text, homePage, bugEmail)
  936.  
  937.     aboutData.addAuthor(ki18n("Jonathan Riddell"), ki18n("Author"))
  938.     aboutData.addAuthor(ki18n("Tim Waugh/Red Hat"), ki18n("System Config Printer Author"))
  939.  
  940.     options = KCmdLineOptions()
  941.     options.add("show", ki18n("Show even when nothing printing"))
  942.  
  943.     KCmdLineArgs.init(sys.argv, aboutData)
  944.     KCmdLineArgs.addCmdLineOptions(options)
  945.  
  946.     app = KApplication()
  947.  
  948.     args = KCmdLineArgs.parsedArgs()
  949.  
  950.     app.setWindowIcon(KIcon("printer"))
  951.     if app.isSessionRestored():
  952.          sys.exit(1)
  953.     applet = JobManager()
  954.     if args.isSet("show"):
  955.         applet.mainWindow.show()
  956.         applet.sysTray.show()
  957.     sys.exit(app.exec_())
  958.